package cx3k.dataaccess;

// don't touch ProfileManager, DatabaseManager

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.SAXException;

import cx3k.GUI.ScheduledCourseGroup;
import cx3k.scheduler.objects.AcademicYear;
import cx3k.scheduler.objects.Course;
import cx3k.scheduler.objects.Schedule;
import cx3k.scheduler.objects.ScheduledSemester;
import cx3k.scheduler.objects.util.CompareUtil;
import cx3k.scheduler.wrappers.CourseWrapper;


/*
 * Note: This class is incomplete and needs to be finished.
 * @author charles
 */
public class ScheduleManager extends FileManager<Schedule> {

	/**
	 * Log information to specified location.
	 */
	private static Logger logger = Logger.getLogger(ScheduleManager.class);
	
	/**
	 * @return	The logger for this class.
	 */
	private static Logger getLogger(){
		return logger;
	}
	
	// XML tag's
	private static final String TAG_SCHEDULES      	= "schedules";
	private static final String TAG_SCHEDULE      	= "schedule";
	private static final String TAG_SEMESTER       	= "semester";
	private static final String TAG_SCHEDCRSGRP		= "scheduledCourseGroup";
	private static final String TAG_CLASS    		= "class";
	
	// XML Settings
	private static final String XML_VERSION        	= "1.0";
	private static final String XML_ENCODING       	= "UTF-8";
	
	private Document xmlDoc = null;
	private String   xmlStr = null;
	
	/**
	 * Create a new instance of the ScheduleManager object.
	 * @param sourceFile is a type File pointing to an XML file for read, write, append, and delete operations.
	 */
	public ScheduleManager(File sourceFile) {
		super(sourceFile);
		xmlDoc = openFile(sourceFile);
	}
	
	public ScheduleManager(String sourceFile){
		this(new File(sourceFile));
	}

	private void tempPrint(String sourceFile)
	{
		System.out.println(sourceFile);
	}
	
	private Document openFile(File sourceFile){
		DOMParser parser = new DOMParser();
		Document document = null;
		
		try {
		        parser.parse(super.getSourceFile().getAbsoluteFile().toString());
		        document = parser.getDocument();
		        
		    } catch (IOException e) {
		        getLogger().error(e);
		    } catch (SAXException e) {
		    	 getLogger().error(e);
			}
		    
		if(document == null)
		    return null;
		return document;
	}
	
	private boolean saveFile(File sourceFile){
		try {
			FileUtils.writeStringToFile(sourceFile, this.generateXMLString(xmlDoc));
		} catch (IOException e) {
			getLogger().error(e);
			return false;
		}
		xmlDoc = openFile(sourceFile);
		return true;
	}

	@Override
	public Schedule delete(int id) { // method works
		Schedule deletedSchedule = null;
		NodeList schedules;
		Node selectedSchedule = null;
		boolean fileChanged = false;

		    schedules = xmlDoc.getElementsByTagName(TAG_SCHEDULE);
		    
		    for(int i = 0; i < schedules.getLength(); i++){
		    	int tempID = new Integer(schedules.item(i).getAttributes().getNamedItem("id").getNodeValue()).intValue();
		    	if(id == tempID){
		    		getLogger().trace("delete(" + id + ") was succesful");
		    		System.out.println("Schedule deleted with id: " + id );
		    		selectedSchedule = schedules.item(i);
		    		fileChanged = true;
		    	}
		    }
		    if(!fileChanged){
		    	getLogger().warn("Couldn't delete '" + id + "', wasn't in schedule!");
		    	return null;
		    }
		    
		    if(selectedSchedule != null){ // fix, removes </schedules> when deleting last schedule in file
		    	// load Schedule object of node that is about to be deleted
		    	deletedSchedule = this.load(id);
		    	//xmlDoc.getFirstChild().removeChild(selectedSchedule); // remove node
		    	Node temp = xmlDoc.getElementsByTagName(TAG_SCHEDULES).item(0);
		    	NodeList tempSchedules = temp.getChildNodes();
		    	
		    	for(int i = 0; i < tempSchedules.getLength(); i++){
		    		if(tempSchedules.item(i).isEqualNode(selectedSchedule))
		    			temp.removeChild(selectedSchedule);
		    	}		    	
		    }
		    System.out.println("delete xmlDoc is now: " + this.generateXMLString(xmlDoc));
		    
		return deletedSchedule;  //object that was just deleted
	}

	@Override
	public void delete(Schedule delete) { // method works
		if(delete == null)
			return;	
		this.delete(delete.getScheduleId());
	}

	@Override
	public Schedule load(int id) { // method works		
		return ParseXML(xmlStr, id);
	}
	
	@Override
	public List<Schedule> loadAll() { // method works
		NodeList schedules;
		List <Schedule> listOfSchedules = new ArrayList<Schedule>();
		
		if(xmlDoc == null)
			return null;
		
		schedules = xmlDoc.getElementsByTagName(TAG_SCHEDULE);
		    
		for(int i = 0; i < schedules.getLength(); i++)
			listOfSchedules.add(this.load(new Integer(schedules.item(i).getAttributes().getNamedItem("id").getNodeValue()).intValue()));
		return listOfSchedules;
	}

	@Override
	public boolean save(Schedule save) { // method incomplete
	  	Element schedules;
	    Element schedule = null;
	    Element semester;
	    Element schedCourseGrp;
	    Element cclass;
	    NodeList schedule_nodes;
	    NodeList semester_nodes;
	    NodeList schedCourseGrp_nodes;
	    boolean newFile = false;
	    boolean appendFile = false;
	    
	    if(save == null)
	    	return false;
	    
	    //saveFile(super.getSourceFile());	    
	    if(xmlDoc == null){
	    	getLogger().warn("New XMLFile being created!");
	    	System.out.println("xmlDoc: " + xmlDoc);
	    	DocumentBuilderFactory dbFactory =
		    	DocumentBuilderFactoryImpl.newInstance();
			DocumentBuilder docBuilder = null;
			
			try {
				docBuilder = dbFactory.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			xmlDoc = docBuilder.newDocument();
			newFile = true;
	    }
	    
	    if(newFile){ // new empty XML file
		    // Create the root element
	    	Date currentTime = new Date();
		    schedules = xmlDoc.createElement(TAG_SCHEDULES);
		    // adjust profile ID creation, look to database that value is unique?
		    xmlDoc.appendChild(xmlDoc.createComment("created on: " + currentTime));
		    schedules.setAttribute("profile", new Integer(save.getScheduleProfile()).toString());
		    System.out.println("profileId: " + new Integer(save.getScheduleProfile()).toString());
	    }
	    else{ // XML file already exists with root
	    	schedules = (Element) xmlDoc.getElementsByTagName(TAG_SCHEDULES).item(0);
	    }
	    	    	
	    if(!newFile && idExists(save.getScheduleId()) != -1){
	    	this.delete(save.getScheduleId());
	    	appendFile = true;
	    }
	   
	    if(newFile || appendFile){
		    schedule = xmlDoc.createElement(TAG_SCHEDULE);
		    schedule.setAttribute("id", new Integer(save.getScheduleId()).toString());
		    schedule.setAttribute("name", save.getScheduleName());
		    System.out.println(schedules);
	    }else{
	    	NodeList tempSchedule = xmlDoc.getElementsByTagName(TAG_SCHEDULE);
	    	
	    	for(int i = 0; i < tempSchedule.getLength(); i++){
	    		if(tempSchedule.item(i).getAttributes().getNamedItem("id").getNodeValue().equals(new Integer(save.getScheduleId()).toString()))
	    			schedule = (Element) tempSchedule.item(i).getFirstChild();
	    		System.out.println("schedule: " + schedule);
	    	}
	    }
	    schedule_nodes = schedules.getChildNodes();
	    
	    /*
	    boolean flag = false;
	    
	    for(int i = 0; i < schedule_nodes.getLength(); i++){
	    	if(schedule_nodes.item(i).isSameNode(schedule))
	    		flag = true; // TODO change
	    }
	    */
	    
	    if(newFile || appendFile)  // Add Schedule Element and its ID
	    	schedules.appendChild(schedule);

	    List<AcademicYear> acadYears = save.getAcademicYears();
	    //System.out.println("acadYears " + acadYears );
	    
	    if(acadYears == null)
	    	return false;
	    
	    for(int i = 0; i < acadYears.size(); i++){
	    	ArrayList<ScheduledSemester> currentSchedSemester = acadYears.get(i).getSemesters() ;
	    	
	    	//System.out.println("acadYear start: " + acadYears.get(i).getStartYear());
	    	
	    	for(int k = 0; k < currentSchedSemester.size(); k++){
	    		ScheduledCourseGroup[] currentSCG = currentSchedSemester.get(k).getCourseGroups();
	    		
	    		semester = xmlDoc.createElement(TAG_SEMESTER);
	    		semester.setAttribute("year", new Integer(currentSchedSemester.get(k).getYear()).toString());
	    		semester.setAttribute("term", currentSchedSemester.get(k).getTerm());
	    			
	    		//System.out.println(currentSchedSemester.get(k).getSemesterId());
			    if(schedule.getElementsByTagName(TAG_SEMESTER).equals(semester))
			    	;
			    else
			    	schedule.appendChild(semester);
	    		
	    		for(int t = 0; t < currentSCG.length; t++){
	    		
	    			schedCourseGrp = xmlDoc.createElement(TAG_SCHEDCRSGRP);
	    			
	    			Course[] currentCourses = currentSCG[t].getCourses();
	    			
	    			for(int m = 0; m < currentCourses.length; m++){
	    				//System.out.println("entry: " + currentCourses[m].getDegreeProgram() + "\t" + currentCourses[m].getCourseNum());
	    				cclass = xmlDoc.createElement(TAG_CLASS);
	    				cclass.setAttribute("id", new Integer(currentCourses[m].getCourseId()).toString());
	    				cclass.setAttribute("sid", new Integer(currentCourses[m].getSpecificSectionId()).toString());
	    				cclass.appendChild(xmlDoc.createTextNode(currentCourses[m].getName()));
	    				schedCourseGrp.appendChild(cclass);
	    				//System.out.print("");
	    			}
	    			semester.appendChild(schedCourseGrp);
	    		}
	    		schedule.appendChild(semester);
	    	}
	    }
	    
	    if(newFile)
	    	schedules.appendChild(schedule);
	    
	    // Add to the root Element
	    if(newFile){
	    	xmlDoc.appendChild(schedules);
	    }
		
		return this.saveFile(super.getSourceFile());
	}
	
	private Schedule ParseXML(String xmlStr, int id){ // method works, needs fixing to work with new scheduled item	
	    //  create a DOM parser
		DOMParser parser = new DOMParser();
		//Document document = null;
		int index = -1;
		NodeList schedules;
		NodeList schedule;
		NodeList semesters;
		NodeList schedCrsGrp;
		NodeList classes;
		Node selectedSchedule;
		Node selectedSemester;
		Node selectedSchedCrsGrp;
		Node selectedClass;
		String profileID = "";
		Schedule theSchedule = new Schedule();
		ScheduledSemester theScheduledSemester = null;
		//AcademicYear theAcademicYear = null;
		ArrayList <ScheduledSemester> schedGroupCrsOfSemester = null;
		//List <Course> courseList = new ArrayList<Course>();
		 //  parse the document  

		    
		    schedules = xmlDoc.getChildNodes().item(0).getChildNodes();
	        selectedSchedule = schedules.item(0);
	        profileID = xmlDoc.getElementsByTagName(TAG_SCHEDULES).item(0).getAttributes().getNamedItem("profile").getNodeValue();
	        
		    index = idExists(id);

		    if(index == -1){  // specified id was not found in schedule
		    	getLogger().warn("Schedule with id of '" + id + "' was not found!");
		        return null;
		    }	    
		    
	        //Element selectedSemester = document.getElementById(new Integer(id).toString());
	        semesters = xmlDoc.getElementsByTagName(TAG_SCHEDULE);
	        
	        if(semesters == null)
	        	return null;
	        else
	        	System.out.println(semesters.item(0).toString());
	        
	        System.out.println("index is " + index);
	        
	        String name = semesters.item(index).getAttributes().getNamedItem("name").getNodeValue();
	        theSchedule.setScheduleName(name);
	        theSchedule.setScheduleProfile(new Integer(profileID).intValue());
	        theSchedule.setScheduleId(id);	        
	        semesters = semesters.item(index).getChildNodes();
	        selectedSemester = semesters.item(0);
	        
    		int startYear = 0;
    		int endYear = 0; 
    		int year = 0;
    		String term = "";
    		boolean StartYear = false;
	        
    		schedGroupCrsOfSemester= new ArrayList<ScheduledSemester>();
    		
	        while(selectedSemester != null){
	        	if(!selectedSemester.getNodeName().equals("#text")){
	        		year = new Integer(selectedSemester.getAttributes().getNamedItem("year").getNodeValue()).intValue();
	        		term = selectedSemester.getAttributes().getNamedItem("term").getNodeValue();
	        		if(!StartYear){
	        			startYear = year;
	        			StartYear = true;
	        		}
		        	theScheduledSemester = new ScheduledSemester(theSchedule.getScheduleId(), year, term);  // TODO
		        	
	        		
	        		endYear = year;
	        		        		
	        		schedCrsGrp = selectedSemester.getChildNodes();
	        		selectedSchedCrsGrp = schedCrsGrp.item(0);
	        		
	        		while(selectedSchedCrsGrp != null){
	        			classes = selectedSchedCrsGrp.getChildNodes();
		        		selectedClass = classes.item(0);
		        		
		        		ScheduledCourseGroup currentCrsGrp = new ScheduledCourseGroup();
		        		
		        		while(selectedClass != null){
		        			if(!selectedClass.getNodeName().equals("#text")){	        				
		        				//Course tempCourse = new Course();
		        				Object tempCourse;
		        				
		        				Integer classId = new Integer(selectedClass.getAttributes().getNamedItem("id").getNodeValue());
		        				Integer sectionId = new Integer(selectedClass.getAttributes().getNamedItem("sid").getNodeValue());
		        				String  className = selectedClass.getTextContent();
		        				//CourseWrapper tempCourse = new CourseWrapper(classId.intValue());
		        				
		        				if(new Integer(classId).intValue() == -1){
		        					// store -1 as classID of class not in database
		        					// load degreeProgram, courseNum, and credits
		        					tempCourse = new Course();
			        				((Course) tempCourse).setCourseId(classId.intValue());
			        				((Course) tempCourse).setDegreeProgram(className.substring(0, 4));
			        				((Course) tempCourse).setCourseNum(className.substring(4, className.length()));
			        				((Course) tempCourse).setSpecificSectionId(-1);
			        				//currentCrsGrp.addCourse(((Course) tempCourse));
		        				}else{
		        					tempCourse = new CourseWrapper(classId.intValue());
		        					tempCourse = ((CourseWrapper) tempCourse).getCourse();
		        					((Course) tempCourse).setSpecificSectionId(sectionId.intValue());	
		        				}
		        				currentCrsGrp.addCourse(((Course) tempCourse));
		        				System.out.print("\tClassID: " + classId.intValue());
		        				System.out.println("\tClassName: " + className);
		        			}
		        			selectedClass = selectedClass.getNextSibling();	
		        		}
		        		
		        		if(!currentCrsGrp.getCoursesList().isEmpty()){
		        			theScheduledSemester.addCourse(currentCrsGrp);
		        			//schedGroupCrsOfSemester.add(currentCrsGrp);
		        		}
	        			selectedSchedCrsGrp = selectedSchedCrsGrp.getNextSibling();
	        		}
	        	}	
	        	selectedSemester = selectedSemester.getNextSibling();
	        	
	        	if(theScheduledSemester != null){
	        		schedGroupCrsOfSemester.add(theScheduledSemester);
	        		theScheduledSemester = null;
	        	}
	        		
        		System.out.println("startYear: " + startYear + " - " + "endYear: " + endYear);
        		if(CompareUtil.compareYears(startYear, endYear) == -1){
        			AcademicYear currentAcadYear = new AcademicYear(startYear, endYear);
        			
        			for(int i = 0; i < schedGroupCrsOfSemester.size(); i++){
        				currentAcadYear.addSemester(schedGroupCrsOfSemester.get(i));
        			}
        			theSchedule.addAcademicYear(currentAcadYear);
        			schedGroupCrsOfSemester= new ArrayList<ScheduledSemester>();
        			StartYear = false;
        			startYear = endYear;
        		}
	        }
		    
	        //Schedule.printSchedule(theSchedule);
		return theSchedule;
	}


	  // Generate String out of the XML document object
	  private String generateXMLString(Node document) { // method works, need to replace with something better
		    DOMImplementationRegistry registry = null;
		    
			try {
				registry = DOMImplementationRegistry.newInstance();
			} catch (ClassCastException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		    DOMImplementationLS impl = 
		        (DOMImplementationLS)registry.getDOMImplementation("LS");

		    LSSerializer writer = impl.createLSSerializer();
		    DOMConfiguration config = writer.getDomConfig();
		    LSOutput output = impl.createLSOutput();
		    //config.setParameter("xml-declaration", xmlDecl);
		    config.setParameter("xml-declaration", true);
		    config.setParameter("format-pretty-print", true);  
		    config.setParameter("well-formed", true);
		    config.setParameter("comments", true);
		    output.setEncoding(XML_ENCODING);
		    output.setByteStream(System.out);
		    writer.write(document, output);
		    document.normalize();

		    return writer.writeToString(document); 
	  }

		private int idExists(int id){
			String scheduleId;
			NodeList schedule;
			
		    schedule = xmlDoc.getElementsByTagName(TAG_SCHEDULE);
		        
		    for(int i = 0; i < schedule.getLength(); i++){
		    	scheduleId = schedule.item(i).getAttributes().getNamedItem("id").getNodeValue();
		    	if(scheduleId.equals(new Integer(id).toString()))
			        return i;
		    }
		        
		    getLogger().warn("Schedule with id of '" + id + "' was not found!");
			return -1;
		}
}


